<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>
      
    线程编程指导 第二章 - Arisu
    
    </title>
    <link rel="shortcut icon" href="https://ushio.oss-cn-shanghai.aliyuncs.com/Arisu/Arisu_Icon.png" type="image/png" />

    
    
    <link href="atom.xml" rel="alternate" title="Arisu" type="application/atom+xml">
    <link rel="stylesheet" href="asset/css/style.min.css">
    <link rel="stylesheet" href="asset/css/doc.css">
    <script src="asset/app.js"></script>
</head>
  <body>
    <section class="hero">
      <div class="hero-head">
          <nav class="navbar" role="navigation" aria-label="main navigation">
              <div class="container">
              <div class="navbar-brand">
                
                <a target="_self" class="navbar-item " href="index.html">主页</a>
                
                <a target="_self" class="navbar-item " href="Apple.html">Apple</a>
                

                <a role="button" id="navbarSNSRssSwitchBtn" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarSNSRssButtons">
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                </a>
              </div>
            
              <div id="navbarSNSRssButtons" class="navbar-menu">
                <div class="navbar-start">
                  
                </div>
            
                <div class="navbar-end">
                  <div class="navbar-item">
                    <!--buttons start-->
                    <div class="buttons">
                      
                        
                        
                        
                        
                      
                      <a href="atom.xml" target="_blank" title="RSS">
                          <span class="icon is-large has-text-black-bis">
                              <svg class="svg-inline--fa fa-rss fa-w-14 fa-lg" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="rss" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M128.081 415.959c0 35.369-28.672 64.041-64.041 64.041S0 451.328 0 415.959s28.672-64.041 64.041-64.041 64.04 28.673 64.04 64.041zm175.66 47.25c-8.354-154.6-132.185-278.587-286.95-286.95C7.656 175.765 0 183.105 0 192.253v48.069c0 8.415 6.49 15.472 14.887 16.018 111.832 7.284 201.473 96.702 208.772 208.772.547 8.397 7.604 14.887 16.018 14.887h48.069c9.149.001 16.489-7.655 15.995-16.79zm144.249.288C439.596 229.677 251.465 40.445 16.503 32.01 7.473 31.686 0 38.981 0 48.016v48.068c0 8.625 6.835 15.645 15.453 15.999 191.179 7.839 344.627 161.316 352.465 352.465.353 8.618 7.373 15.453 15.999 15.453h48.068c9.034-.001 16.329-7.474 16.005-16.504z"></path></svg><!-- <i class="fas fa-rss fa-lg"></i> -->
                          </span>
                      </a>
                    </div>
                    <!--buttons end-->

                  </div>
                </div>
                </div>
              </div>
            </nav>
      </div>

 <div class="hero-body ct-body"></div>
      
    </section>
    <section class="ct-body">
      <div class="container">
          <div class="columns is-variable bd-klmn-columns is-4 is-centered">
              <div class="column is-four-fifths">
                  <div class="post-body single-content">
                    
                    <h1 class="title">
                            线程编程指导 第二章   
                      </h1>
                     
                    
                      <div class="media">
                            
                            <figure class="media-left">
                              <p class="image is-48x48">
                                
                                  <img class="is-rounded" src="https://ushio.oss-cn-shanghai.aliyuncs.com/Arisu/Arisu_Icon.png">
                                
                              </p>
                            </figure>
                            
                            <div class="media-content">
                              <div class="content">
                                <p>
                                 <span class="date">2020/02/25</span>
                                  <span class="tran-posted-in">posted in</span>&nbsp; 
                                  
                                      <span class="posted-in"><a href='Apple%20Developer%20Documentation.html'>Apple Developer Documentation</a></span>
                                  
                                      <span class="posted-in"><a href='%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E6%8C%87%E5%AF%BC.html'>线程编程指导</a></span>
                                         
                                  

                                   
                                      
                                  <br />
                                  <span class="tran-tags">Tags:</span>&nbsp;
                                  
                                    <a class="tag is-link is-light" href='tag_%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3.html'>#技术文档</a>
                                  
                                    <a class="tag is-link is-light" href='tag_Arisu.html'>#Arisu</a>
                                     

                                </p>
                              </div>
                            </div>
                         
                    </div>
                </div>
                  <article class="markdown-body single-content">
                    <h1><a id="%E7%BA%BF%E7%A8%8B%E7%AE%A1%E7%90%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>线程管理</h1>
<p>OS X 或 iOS 中的每个进程（应用程序）都由一个或多个线程组成，每个线程代表通过应用程序代码执行的单个路径。每个应用程序都以一个线程开始，该线程运行应用程序的主要功能。应用程序可以生成其他线程，每个线程都执行特定函数的代码。</p>
<p>当应用程序生成新线程时，该线程将成为应用程序进程空间内的独立实体。每个线程都有自己的执行堆栈，并由内核单独调度运行时（runtime）。线程可以与其他线程和其他进程通信，执行 I / O 操作，并执行您可能需要执行的任何操作。<u>但是，由于它们位于同一进程空间内，因此单个应用程序中的所有线程共享相同的虚拟内存空间，并具有与进程本身相同的访问权限。</u></p>
<p>本章概述了 OS X 和 iOS 中可用的线程技术，以及如何在应用程序中使用这些技术的示例。</p>
<blockquote>
<p>注意：有关 Mac OS 线程体系结构的历史记录以及有关线程的其他背景信息，请参阅技术说明 TN2028，“线程体系结构”。</p>
</blockquote>
<h2><a id="%E7%BA%BF%E7%A8%8B%E6%88%90%E6%9C%AC" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>线程成本</h2>
<p>在内存使用和性能方面，线程对程序（和系统）来说是一个真正的成本。每个线程都需要在内核内存空间和程序的内存空间中分配内存。管理线程和协调其调度所需的核心结构使用有线内存存储在内核中。线程的堆栈空间和每线程数据存储在程序的内存空间中。大多数这些结构都是在您第一次创建线程时创建和初始化的 - 由于与内核的所需交互，该过程可能相对昂贵。</p>
<p>表 2-1 量化了与在应用程序中创建新的用户级线程相关的大致成本。其中一些成本是可配置的，例如为辅助线程分配的堆栈空间量。创建线程的时间成本是粗略的近似值，应该仅用于相互比较。根据处理器负载，计算机速度以及可用系统和程序存储器的数量，线程创建时间可能会有很大差异。</p>
<p>表 2-1：创建线程成本</p>
<table>
<thead>
<tr>
<th>项目</th>
<th style="text-align: center">近似成本</th>
<th style="text-align: center">笔记</th>
</tr>
</thead>
<tbody>
<tr>
<td>内核数据结构</td>
<td style="text-align: center">大约 1 KB</td>
<td style="text-align: center">此内存用于存储线程数据结构和属性，其中大部分被分配为有线内存，因此无法分页到磁盘。</td>
</tr>
<tr>
<td>堆栈空间</td>
<td style="text-align: center">512 KB（辅助线程） 8 MB（OS X 主线程） 1 MB（iOS 主线程）</td>
<td style="text-align: center">辅助线程允许的最小堆栈大小为 16 KB，堆栈大小必须是 4 KB 的倍数。在创建线程时，您的进程空间中会留出此内存的空间，但在需要之前不会创建与该内存关联的实际页面。</td>
</tr>
<tr>
<td>初始化时间</td>
<td style="text-align: center">大约 90 微秒</td>
<td style="text-align: center">此值反映了创建线程的初始调用与线程的入口点例程开始执行的时间之间的时间。这些数据是通过分析在基于 Intel 的 iMac 上使用 2 GHz  Core Duo 处理器和运行 OS X v10.5 的 1 GB RAM创建线程时生成的平均值和中值来确定的。</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注意：<u>由于它们的底层内核支持，操作对象通常可以更快地创建线程。他们不是每次都从头开始创建线程，而是使用已驻留在内核中的线程池来节省分配时间。</u>有关使用操作对象的更多信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091">Concurrency Programming Guide</a>。</p>
</blockquote>
<p>编写线程代码时要考虑的另一个成本是生产成本。设计线程化应用程序有时可能需要对组织应用程序数据结构的方式进行根本性更改。进行这些更改可能是必要的，以避免使用同步，这本身可能会对设计不佳的应用程序造成巨大的性能损失。设计这些数据结构以及调试线程代码中的问题会增加开发线程应用程序所需的时间。但是，如果你的线程花太多时间等待锁或什么也不做，避免这些成本会在运行时产生更大的问题。</p>
<h2><a id="%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>创建一个线程</h2>
<p>创建底层线程相对简单。在所有情况下，您必须具有一个函数或方法来充当线程的主入口点，并且必须使用一个可用的线程例程来启动线程。以下部分显示了常用的线程技术的基本创建过程。使用这些技术创建的线程会继承一组默认属性，这些属性由您使用的技术决定。有关如何配置线程的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW8">配置线程属性</a>。</p>
<h3><a id="%E4%BD%BF%E7%94%A8nsthread" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSThread</h3>
<p>使用 NSThread 类创建线程有两种方法：</p>
<ul>
<li>使用 <code>detachNewThreadSelector:toTarget:withObject:</code> class 方法生成新线程。</li>
<li>创建一个新的 <code>NSThread</code> 对象并调用其 <code>start</code> 方法。（仅在 iOS 和 OS X v10.5 及更高版本中受支持。）</li>
</ul>
<p>因为 OS X 的所有版本都支持 <code>detachNewThreadSelector:toTarget:withObject:</code> 方法，所以经常在使用线程的现有 Cocoa 应用程序中找到它。要分离新线程，只需提供要用作线程入口点的方法名称（指定为选择器），定义该方法的对象以及要在启动时传递给线程的任何数据。以下示例显示了此方法的基本调用，该方法使用当前对象的自定义方法生成线程。</p>
<pre><code class="language-objectivec">NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
</code></pre>
<p>在 OS X v10.5 之前，您主要使用 <code>NSThread</code> 类来生成线程。虽然您可以获取 <code>NSThread</code> 对象并访问某些线程属性，但您只能在线程运行后从线程本身执行此操作。在 OS X v10.5 中，添加了对创建 <code>NSThread</code> 对象的支持，而不会立即生成相应的新线程。（此支持也可在 iOS 中使用。）此支持使得在启动线程之前获取和设置各种线程属性成为可能。它还使得以后可以使用该线程对象来引用正在运行的线程。</p>
<p>在 OS X v10.5 及更高版本中初始化 <code>NSThread</code> 对象的简单方法是使用 <code>initWithTarget:selector:object:</code> 方法。此方法获取与<code>detachNewThreadSelector:toTarget:withObject:</code> 方法完全相同的信息，并使用它来初始化新的 <code>NSThread</code> 实例。但是，它不会启动该线程。要启动该线程，请显式调用线程对象的 <code>start</code> 方法，如以下示例所示：</p>
<pre><code class="language-objectivec">NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];
[myThread start];  // Actually create the thread
</code></pre>
<blockquote>
<p>注意：使用 <code>initWithTarget:selector:object:</code> 方法的替代方法是子类化 <code>NSThread</code> 并覆盖其主方法。您将使用此方法的重写版本来实现线程的主入口点。有关更多信息，请参阅 <a href="https://developer.apple.com/documentation/foundation/nsthread">NSThread 类参考</a>中的子类注释。</p>
</blockquote>
<p>如果你有一个当前正在运行线程的 <code>NSThread</code> 对象，你可以向该线程发送消息的一种方法是使用 <code>performSelector:onThread:withObject:waitUntilDone:</code> 应用程序中几乎任何对象的方法。在 OS X v10.5 中引入了对线程（主线程除外）上执行选择器的支持，这是在线程之间进行通信的便捷方式。（此支持在 iOS 中也可用。）使用此技术发送的消息由其他线程直接执行，作为其正常运行循环处理的一部分。（当然，这确实意味着目标线程必须在其运行循环中运行；请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW2">运行循环</a>。）当您以这种方式进行通信时，您可能仍需要某种形式的同步，但它比在线程之间设置通信端口更简单。</p>
<blockquote>
<p>注意：尽管线程之间偶尔进行通信很好，但不应使用 <code>performSelector:onThread:withObject:waitUntilDone:</code> 方法来处理线程之间的紧急的通信或使用该方法频繁通信。</p>
</blockquote>
<p>有关其他线程通信选项的列表，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW3">设置线程的分离状态</a>。</p>
<h3><a id="%E4%BD%BF%E7%94%A8posix%E7%BA%BF%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 POSIX 线程</h3>
<p>OS X 和 iOS 为使用 POSIX 线程 API 创建线程提供了基于 C 的支持。该技术实际上可以用于任何类型的应用程序（包括 Cocoa 和 Cocoa Touch 应用程序），如果您为多个平台编写软件，可能会更方便。用于创建线程的 POSIX 例程足够恰当地调用 <code>pthread_create</code>。</p>
<p>表 2-1 显示了使用 POSIX 调用创建线程的两个自定义函数。<code>LaunchThread</code> 函数创建一个新线程，其主程序在 <code>PosixThreadMainRoutine</code> 函数中实现。因为默认情况下 POSIX 将线程创建为可连接，所以此示例更改线程的属性以创建分离线程。<u>将线程标记为已分离使系统有机会在退出时立即回收该线程的资源。</u></p>
<p>表 2-1：使用 C 创建一个线程</p>
<pre><code class="language-c">#include &lt;assert.h&gt;
#include &lt;pthread.h&gt;
 
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
 
    return NULL;
}
 
void LaunchThread()
{
    // Create the thread using POSIX routines.
    pthread_attr_t  attr;
    pthread_t       posixThreadID;
    int             returnVal;
 
    returnVal = pthread_attr_init(&amp;attr);
    assert(!returnVal);
    returnVal = pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);
    assert(!returnVal);
 
    int     threadError = pthread_create(&amp;posixThreadID, &amp;attr, &amp;PosixThreadMainRoutine, NULL);
 
    returnVal = pthread_attr_destroy(&amp;attr);
    assert(!returnVal);
    if (threadError != 0)
    {
         // Report an error.
    }
}
</code></pre>
<p>如果将前面列表中的代码添加到其中一个源文件并调用 <code>LaunchThread</code> 函数，它将在您的应用程序中创建一个新的分离线程。当然，使用此代码创建的新线程不会做任何有用的事情。线程将启动并几乎立即退出。为了使事情更有趣，您需要将代码添加到 <code>PosixThreadMainRoutine</code> 函数以执行一些实际工作。为了确保线程知道要做什么工作，您可以在创建时向其传递指向某些数据的指针。您将此指针作为 <code>pthread_create</code> 函数的最后一个参数传递。</p>
<p>要将新创建的线程中的信息传递回应用程序的主线程，您需要在目标线程之间建立通信路径。对于基于 C 的应用程序，有多种方法可以在线程之间进行通信，包括使用端口，条件或共享内存。对于长期存在的线程，您几乎应该始终设置某种线程间通信机制，以便为应用程序的主线程提供一种检查线程状态的方法，或者在应用程序退出时干净地关闭它。</p>
<p>有关POSIX线程函数的更多信息，请参见 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pthread.3.html#//apple_ref/doc/man/3/pthread">pthread 手册页</a>。</p>
<h3><a id="%E4%BD%BF%E7%94%A8nsobject%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSObject 生成一个线程</h3>
<p>在 iOS 和 OS X v10.5 及更高版本中，所有对象都能够生成新线程并使用它来执行其中一个方法。<code>performSelectorInBackground:withObject:</code> 方法创建一个新的分离线程，并使用指定的方法作为新线程的入口点。例如，如果你有一个对象（由变量 <code>myObj</code> 表示）并且该对象有一个你想在后台线程中运行的 <code>doSomething</code> 方法，你可以使用以下代码来做到这一点：</p>
<pre><code class="language-objectivec">[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
</code></pre>
<p>调用此方法的效果与调用 <code>detachNewThreadSelector:toTarget:withObject:</code> 的 <code>NSThread</code> 的方法（当前对象，选择器和参数对象作为参数）相同。使用默认配置立即生成新线程并开始运行。在选择器内部，您必须像处理任何线程一样配置线程。例如，如果您计划使用它，则需要设置自动释放池（如果您没有使用垃圾收集）并配置线程的运行循环。有关如何配置新线程的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW8">配置线程属性</a>。</p>
<h3><a id="%E5%9C%A8cocoa%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E4%BD%BF%E7%94%A8-posix%E7%BA%BF%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>在 Cocoa 应用程序中使用 POSIX 线程</h3>
<p>尽管 <code>NSThread</code> 类是在 Cocoa 应用程序中创建线程的主要接口，但是如果这样做对您来说更方便，则可以自由使用 POSIX 线程。例如，如果您已经拥有使用它们的代码并且您不想重写它，则可以使用 POSIX 线程。如果您计划在 Cocoa 应用程序中使用 POSIX 线程，您仍应该了解 Cocoa 和线程之间的交互，并遵守以下各节中的准则。</p>
<h4><a id="%E4%BF%9D%E6%8A%A4cocoa%E6%A1%86%E6%9E%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>保护 Cocoa 框架</h4>
<p>对于多线程应用程序，Cocoa 框架使用锁和其他形式的内部同步来确保它们的行为正确。但是，为了防止这些锁在单线程情况下降低性能，Cocoa 不会创建它们，直到应用程序使用 <code>NSThread</code> 类生成其第一个新线程。<u>如果仅使用 POSIX 线程例程生成线程，Cocoa 不会收到它需要知道您的应用程序现在是多线程的通知。当发生这种情况时，涉及 Cocoa 框架的操作可能会使您的应用程序不稳定或崩溃。</u></p>
<p>为了让 Cocoa 知道你打算使用多个线程，你所要做的就是使用 <code>NSThread</code> 类生成一个线程并让该线程立即退出。你的线程入口点不需要做任何事情。只是使用 <code>NSThread</code> 生成线程的行为足以确保 Cocoa 框架所需的锁到位。</p>
<p>如果您不确定 Cocoa 是否认为您的应用程序是多线程的，则可以使用 <code>NSThread</code> 的 <code>isMultiThreaded</code> 方法进行检查。</p>
<h4><a id="%E6%B7%B7%E5%90%88posix%E5%92%8C-cocoa%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>混合 POSIX 和 Cocoa 锁</h4>
<p>在同一个应用程序中使用 POSIX 和 Cocoa 锁的混合是安全的。Cocoa 锁和条件对象基本上只是 POSIX 互斥和条件的包装器。但是，对于给定的锁，必须始终使用相同的接口来创建和操作该锁。换句话说，您不能使用 Cocoa <code>NSLock</code> 对象来操作使用 <code>pthread_mutex_init</code> 函数创建的互斥锁，反之亦然。</p>
<h2><a id="%E9%85%8D%E7%BD%AE%E7%BA%BF%E7%A8%8B%E5%B1%9E%E6%80%A7" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>配置线程属性</h2>
<p>在创建线程之后，有时在之前，您可能希望配置线程环境的不同部分。以下部分介绍了您可以进行的一些更改以及何时可以进行的更改。</p>
<h3><a id="%E9%85%8D%E7%BD%AE%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%A0%86%E6%A0%88%E5%A4%A7%E5%B0%8F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>配置线程的堆栈大小</h3>
<p>对于您创建的每个新线程，系统会在进程空间中分配特定数量的内存，以充当该线程的堆栈。堆栈管理堆栈帧，也是声明线程的任何局部变量的地方。为线程分配的内存量列在<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW7">线程成本</a>中。</p>
<p>如果要更改给定线程的堆栈大小，则必须在创建线程之前执行此操作。尽管使用 <code>NSThread</code> 设置堆栈大小仅在 iOS 和 OS X v10.5 及更高版本中可用，但所有线程技术都提供了一些设置堆栈大小的方法。表 2-2 列出了每种技术的不同选项。</p>
<p>表 2-2：设置线程的堆栈大小</p>
<table>
<thead>
<tr>
<th>技术</th>
<th style="text-align: center">选项</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cocoa</td>
<td style="text-align: center">在 iOS 和 OS X v10.5 及更高版本中，分配并初始化 <code>NSThread</code> 对象（不要使用 <code>detachNewThreadSelector:toTarget:withObject:</code> 方法）。在调用线程对象的 <code>start</code> 方法之前，请使用 <code>setStackSize:</code> 方法指定新的堆栈大小。</td>
</tr>
<tr>
<td>POSIX</td>
<td style="text-align: center">创建一个新的 <code>pthread_attr_t</code> 结构并使用 <code>pthread_attr_setstacksize</code> 函数更改默认堆栈大小。创建线程时将属性传递给 <code>pthread_create</code> 函数。</td>
</tr>
<tr>
<td>多处理服务</td>
<td style="text-align: center">在创建线程时，将适当的堆栈大小值传递给 <code>MPCreateTask</code> 函数。</td>
</tr>
</tbody>
</table>
<h3><a id="%E9%85%8D%E7%BD%AE%E7%BA%BF%E7%A8%8B%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>配置线程本地存储</h3>
<p>Cocoa 和 POSIX 以不同的方式存储线程字典，因此您无法混合和匹配对这两种技术的调用。但是，只要您在线程代码中坚持使用一种技术，最终结果应该是相似的。在 Cocoa 中，您使用 <code>NSThread</code> 对象的 <code>threadDictionary</code> 方法来检索 <code>NSMutableDictionary</code> 对象，您可以向该对象添加线程所需的任何键。在 POSIX中，您使用 <code>pthread_setspecific</code> 和 <code>pthread_getspecific</code> 函数来设置和获取线程的键和值。</p>
<h3><a id="%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%88%86%E7%A6%BB%E7%8A%B6%E6%80%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>设置线程的分离状态</h3>
<p><u>大多数高级线程技术默认创建分离线程。在大多数情况下，首选分离线程是因为它们允许系统在完成线程后立即释放线程的数据结构。</u>分离的线程也不需要与您的程序进行明确的交互。从线程中检索结果的方法由您自行决定。<u>相比之下，系统不会回收可连接线程的资源，直到另一个线程显式加入该线程，</u>这个进程可能会阻塞执行连接的线程。</p>
<p>您可以将可连接线程视为类似于子线程。尽管它们仍然作为独立线程运行，但是在系统可以回收其资源之前，必须由另一个线程连接可连接线程。可连接线程还提供了一种将数据从现有线程传递到另一个线程的显式方法。在它退出之前，可连接的线程可以将数据指针或其他返回值传递给 <code>pthread_exit</code> 函数。然后，另一个线程可以通过调用 <code>pthread_join</code> 函数来声明此数据。</p>
<blockquote>
<p>重要：在应用程序退出时，分离的线程可以立即终止，但可连接的线程不能。必须先连接每个可连接线程，然后才允许进程退出。因此，在线程正在执行不应被中断的关键工作（例如将数据保存到磁盘）的情况下，可连接线程可能是优选的。</p>
</blockquote>
<p><u>如果您确实想要创建可连接的线程，唯一的方法是使用 POSIX 线程。</u>默认情况下，POSIX 将线程创建为可连接。要将线程标记为已分离或可连接，请在创建线程之前使用 <code>pthread_attr_setdetachstate</code> 函数修改线程属性。线程开始后，您可以通过调用 <code>pthread_detach</code> 函数将可连接线程更改为分离线程。有关这些 POSIX 线程函数的更多信息，请参见 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pthread.3.html#//apple_ref/doc/man/3/pthread">pthread</a> 手册页。有关如何加入线程的信息，请参见 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pthread_join.3.html#//apple_ref/doc/man/3/pthread_join">pthread_join</a> 手册页。</p>
<h2><a id="%E7%BC%96%E5%86%99%E7%BA%BF%E7%A8%8B%E5%85%A5%E5%8F%A3%E4%BE%8B%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>编写线程入口例程</h2>
<p>在大多数情况下，线程的入口点例程的结构在 OS X 中与在其他平台上的结构相同。初始化数据结构，执行某些操作或者可选地设置运行循环，并在线程代码完成时进行清理。根据您的设计，在编写入门例程时可能需要执行一些额外的步骤。</p>
<h3><a id="%E5%88%9B%E5%BB%BA%E8%87%AA%E5%8A%A8%E9%87%8A%E6%94%BE%E6%B1%A0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>创建自动释放池</h3>
<p>在 objectivec 框架中链接的应用程序通常必须在每个线程中创建至少一个自动释放池。如果应用程序使用托管模型 - 应用程序处理保留和释放对象的位置 - 自动释放池将捕获从该线程自动释放的任何对象。
<u>如果应用程序使用垃圾收集而不是托管内存模型，则不一定要创建自动释放池。在垃圾收集的应用程序中存在自动释放池是无害的，并且在很大程度上被忽略。</u>允许代码模块必须同时支持垃圾收集和托管内存模型的情况。在这种情况下，必须存在自动释放池以支持托管内存模型代码，如果应用程序在启用了垃圾收集的情况下运行，则会被忽略。
如果您的应用程序使用托管内存模型，那么创建自动释放池应该是您在线程入口例程中首先要做的事情。同样，销毁这个自动释放池应该是你在线程中做的最后一件事。此池确保捕获自动释放的对象，但在线程本身退出之前不会释放它们。表 2-2 显示了使用自动释放池的基本线程入口例程的结构。</p>
<p>表 2-2：定义你的线程入口程序例程</p>
<pre><code class="language-c">- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
 
    // Do thread work here.
 
    [pool release];  // Release the objects in the pool.
</code></pre>
<p>因为顶级自动释放池在线程退出之前不释放其对象，所以长期存在的线程应该创建额外的自动释放池以更频繁地释放对象。例如，使用运行循环的线程可能每次通过该运行循环创建并释放自动释放池。更频繁地释放对象可以防止应用程序的内存占用过大，从而导致性能问题。与任何与性能相关的行为一样，您应该测量代码的实际性能并适当调整自动释放池的使用。</p>
<p>有关内存管理和自动释放池的详细信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011i">Advanced Memory Management Programming Guide</a>。</p>
<h3><a id="%E8%AE%BE%E7%BD%AE%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%A8%8B%E5%BA%8F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>设置异常处理程序</h3>
<p>如果您的应用程序捕获并处理异常，则应准备好线程代码以捕获可能发生的任何异常。尽管最好在异常发生时处理异常，但是在线程中捕获异常异常会导致应用程序退出。在线程入口例程中安装最终的 try / catch 允许您捕获任何未知异常并提供适当的响应。</p>
<p>在 Xcode 中构建项目时，可以使用 C ++ 或 objectivec 异常处理样式。有关设置如何在 objectivec 中引发和捕获异常的信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html#//apple_ref/doc/uid/10000012i">Exception Programming Topics</a>。</p>
<h3><a id="%E8%AE%BE%E7%BD%AE%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>设置运行循环</h3>
<p>在编写要在单独的线程上运行的代码时，您有两个选择。第一个选项是将线程的代码编写为一个长任务，在很少或没有中断的情况下执行，并在完成时让线程退出。第二个选项是将您的线程置于一个循环中，让它在到达时动态处理请求。第一个选项不需要为您的代码进行特殊设置；你刚开始做你想做的工作。但是，第二个选项涉及设置线程的运行循环。</p>
<p>OS X 和 iOS 为在每个线程中实现运行循环提供内置支持。应用程序框架自动启动应用程序主线程的运行循环。如果创建任何辅助线程，则必须配置运行循环并手动启动它。</p>
<p>有关使用和配置运行循环的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1">运行循环</a>。</p>
<h2><a id="%E7%BB%88%E6%AD%A2%E7%BA%BF%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>终止线程</h2>
<p>退出线程的推荐方法是让它正常退出其入口点例程。<u>尽管 Cocoa，POSIX 和 多处理器服务提供了直接杀死线程的例程，但强烈建议不要使用此类例程。杀死一个线程会防止该线程自行清理。线程分配的内存可能会被泄露，并且线程当前正在使用的任何其他资源可能无法正确清理，从而在以后产生潜在问题。</u></p>
<p>如果您预计需要在操作过程中终止线程，则应该从一开始就设计线程以响应取消或退出消息。对于长时间运行的操作，这可能意味着定期停止工作并检查是否有这样的消息到达。如果确实有消息要求线程退出，则线程将有机会执行任何所需的清理并正常退出。否则，它可以简单地返回工作并处理下一个数据块。</p>
<p>响应取消消息的一种方法是使用运行循环输入源来接收此类消息。表 2-3 显示了此代码在线程的主入口例程中的外观结构。（该示例仅显示主循环部分，不包括设置自动释放池或配置要执行的实际工作的步骤。）该示例在运行循环上安装自定义输入源，可能是从另一个你的线程；有关设置输入源的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW7">配置运行循环源</a>。执行总工作量的一部分后，线程会短暂运行运行循环，以查看是否有消息到达输入源。如果没有，则运行循环立即退出，循环继续下一个工作块。因为处理程序不能直接访问 <code>exitNow</code> 局部变量，所以退出条件通过线程字典中的键值对进行传递。</p>
<p>表 2-3：在长时间工作期间检查退出条件</p>
<pre><code class="language-objectivec">- (void)threadMainRoutine
{
    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
 
    // Add the exitNow BOOL to the thread dictionary.
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@&quot;ThreadShouldExitNow&quot;];
 
    // Install an input source.
    [self myInstallCustomInputSource];
 
    while (moreWorkToDo &amp;&amp; !exitNow)
    {
        // Do one chunk of a larger body of work here.
        // Change the value of the moreWorkToDo Boolean when done.
 
        // Run the run loop but timeout immediately if the input source isn't waiting to fire.
        [runLoop runUntilDate:[NSDate date]];
 
        // Check to see if an input source handler changed the exitNow value.
        exitNow = [[threadDict valueForKey:@&quot;ThreadShouldExitNow&quot;] boolValue];
    }
}
</code></pre>
<div style="padding-top:25px;">
    <div style="float:left;">
        <a style="text-decoration:none" href="%E5%85%B3%E4%BA%8E%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B.html">&laquo; 上一章：关于线程编程</a>
    </div>
    <div style="float:right">
        <a style="text-decoration:none" href="%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF.html">下一章：运行循环（Run Loops） &raquo;</a> 
    </div>
</div>

                  </article>
                  <div class="comments-wrap">
                    <div class="share-comments">
                      

                      

                      
                    </div>
                  </div><!-- end comments wrap -->
              </div>
            </div><!-- end columns -->
      </div><!-- end container -->
    </section>



    <footer class="footer">
        <div class="content has-text-centered">
          <p>
              Copyright &copy; 2019
              Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,&nbsp; 
              Theme used <a target="_blank" href="https://bulma.io/">Bulma CSS</a>.
          </p>
        </div>
      </footer>



  













<script src="asset/prism.js"></script>



  
    




  </body>
</html>
